package com.oig.sys.oauth.controller;

import cn.hutool.core.util.StrUtil;
import com.oig.common.exception.BadRequestException;
import com.oig.common.module.SysResponse;
import com.oig.common.util.SpringContextUtil;
import com.oig.sys.oauth.util.FreemarkerUtil;
import com.oig.sys.security.util.SecurityUtil;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;

/**
 * oauth2 4种模式
 * 一、授权码模式 步骤
 * 1、请求授权码
 * https://xxx/oauth/authorize?
 *   response_type=code&
 *   client_id=CLIENT_ID&
 *   redirect_uri=CALLBACK_URL&
 *   scope=read
 *
 * 2、授权服务器，根据redirect_uri 返回  code
 *
 *    授权处理过程：
 *    oauth/authorize请求被拦截，如果没有登录跳到登录界面，登录，跳回oauth/authorize
 *
 *    authorize 接口 根据response_type判断模式
 *    校验
 *       redirect_uri 需要与 oauth_client_details表对应的 web_server_redirect_uri 一致
 *       scope  oauth_client_details表对应的 scope要有
 *    如果 autoapprove 字段为true 带code重定向到redirect_uri
 *    否则跳到 授权确认界面 同意授权 带code重定向到redirect_uri
 *
 *
 * 3、根据授权码去获取token
 * https://xxx/oauth/token?
 *  client_id=CLIENT_ID&
 *  client_secret=CLIENT_SECRET&
 *  grant_type=authorization_code&
 *  code=AUTHORIZATION_CODE&
 *  redirect_uri=CALLBACK_URL
 *
 * 4、授权服务器，根据 redirect_uri 返回access_token
 *
 *
 *
 * 二、隐式模式（implicit）：            oauth2.1 已默认不支持
 * 1、客户端直接要求返回token
 * https://xxx/oauth/authorize?
 *   response_type=token&
 *   client_id=CLIENT_ID&
 *   redirect_uri=CALLBACK_URL&
 *   scope=read
 *
 * 2、授权服务器
 *  根据response_type 判断
 *  根据 redirect_uri 返回access_token
 *
 * 三、密码模式（password）：         oauth2.1 已默认不支持
 * https://xxx/oauth/token?
 *   grant_type=password&
 *   username=USERNAME&
 *   password=PASSWORD&
 *   client_id=CLIENT_ID
 *
 *
 *
 * 四、凭证模式（client_credentials）：
 * https://xxx/oauth/token?
 *   grant_type=client_credentials&
 *   client_id=CLIENT_ID&
 *   client_secret=CLIENT_SECRET
 *
 *
 * 五、刷新token
 * https://xxx/oauth/token?
 *   grant_type=refresh_token&
 *   client_id=CLIENT_ID&
 *   client_secret=CLIENT_SECRET&
 *   refresh_token=REFRESH_TOKEN
 *
 */
@Tag(name = "sso")
@RequiredArgsConstructor
@Controller
@RequestMapping("/sso")
@Slf4j
public class AuthenticationController {

    private final OAuth2AuthorizationService oAuth2AuthorizationService;
    private final RegisteredClientRepository registeredClientRepository;


    /**
     * 去登录页面
     */
    @RequestMapping(value = "/loginPage",method = {RequestMethod.POST,RequestMethod.GET})
    public ModelAndView loginPage(HttpServletRequest request,ModelAndView modelAndView) {
        String error = null;
        AuthenticationException exception = (AuthenticationException) request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
        if (exception instanceof UsernameNotFoundException || exception instanceof BadCredentialsException) {
            error = "用户名或密码错误";
        } else if (exception instanceof DisabledException) {
            error = "账户已禁用";
        } else if (exception instanceof LockedException) {
            error = "账户已锁定";
        } else if (exception instanceof AccountExpiredException) {
            error = "账户已过期";
        } else if (exception instanceof CredentialsExpiredException) {
            error = "证书已过期";
        }
        log.info("to login page，{}",error);
        modelAndView.setViewName("ftl/login");
        modelAndView.addObject("error", error);
        return modelAndView;
    }

    /**
     * 去确认页面
     */
    @GetMapping(value = "/confirm_access")
    public ModelAndView confirm(Principal principal, ModelAndView modelAndView,
                                @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
                                @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
                                @RequestParam(OAuth2ParameterNames.STATE) String state){
        log.info("p:{}", principal);
        RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId);
        if (registeredClient == null) {
            throw new BadRequestException("client Id 不存在") ;
        }
        modelAndView.addObject("clientId", clientId);
        modelAndView.addObject("clientName",registeredClient.getClientName());
        //modelAndView.addObject("clientUrl", registeredClient.getRedirectUris().toArray()[0]);
        modelAndView.addObject("state", state);
        modelAndView.addObject("scopeList", registeredClient.getScopes());
        modelAndView.addObject("s_scope", scope);
        modelAndView.addObject("principalName", principal.getName());
        modelAndView.setViewName("ftl/confirm");
        return modelAndView;
    }


    /**
     * 界面退出登录
     */
    @RequestMapping(value = "/logout")
    @ResponseBody
    public SysResponse<String> logout(@RequestParam("accessToken") String accessToken){
        log.info("access_token：{}", accessToken);
        OAuth2Authorization authorization = oAuth2AuthorizationService.findByToken(accessToken, OAuth2TokenType.ACCESS_TOKEN);
        if(authorization != null){
            OAuth2Authorization.Token<OAuth2AccessToken> oAuth2AccessToken = authorization.getAccessToken();
            if (oAuth2AccessToken == null || StrUtil.isBlank(oAuth2AccessToken.getToken().getTokenValue())) {
                return SysResponse.ok();
            }
            log.info("remove access_token");
            oAuth2AuthorizationService.remove(authorization);
            // 处理自定义退出事件，保存相关日志
            SpringContextUtil.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken(
                    authorization.getPrincipalName(), authorization.getRegisteredClientId())));
        }
        return SysResponse.ok();
    }


    /**
     * 测试一下freemarker
     */
    @ResponseBody
    @RequestMapping(value = "/test",method = {RequestMethod.GET, RequestMethod.POST})
    public String test(){
        log.info("getPrincipal : {}",SecurityUtil.getAuthentication().getPrincipal());
        Map<String, Object> map = new HashMap<>();
        map.put("message","FreemarkerUtil") ;
        return FreemarkerUtil.parseTpl("ftl/test", map);
    }

}